from pyparsing import *
import string
from sympy import *
import Scope

def sub_lists(list1, list2):
    ret = list(list1)
    for item in list2:
        try:
            ret.remove(item)
        except:
            pass
    return ret

def operatorOperands(tokenlist):
    "generator to extract operators and operands in pairs"
    it = iter(tokenlist)
    while 1:
        try:
            yield (it.next(), it.next())
        except StopIteration:
            break

def generalParseAction(s, loc, toks):
    print s
    print loc
    print toks


vars = {}

def assertVariableName(varname):
    if not vars.has_key(varname):
        vars[varname]=symbols(varname)
    return vars[varname]

def multParseAction(s, loc, toks):
    return toks[0][0]*toks[0][2]

def sumParseAction(s, loc, toks):
    value = toks[0]
    prod = value[0]
    for op,val in operatorOperands(value[1:]):
        if op == '+':
            prod+=val
        else:
            prod-=val
    return prod

def signParseAction(s, loc, toks):
    if toks[0][0] == '-':
        return -1*toks[0][1]
    return toks[0][1]

def intParseAction(s, loc, toks):
    return int(toks[0])

def floatParseAction(s, loc, toks):
    return float(toks[0])

def varParseAction(s, loc, toks):
    return assertVariableName(toks[0])

### BNF Dictionary:
          
#Literals:
Comma          = Literal(',')
Underscore     = Literal('_')
LEFT_PAR       = Literal('(')
RIGHT_PAR      = Literal(')')
LEFT_BRAC      = Literal('{')
RIGHT_BRAC     = Literal('}')
EQUALS         = Literal('=')
PLUS           = Literal('+')
MINUS          = Literal('-')
TIMES          = Literal('*').suppress()
signOp         = oneOf('-')
multOp         = oneOf('* /')
sumOp          = oneOf('+ -')
boxOp          = '|' or ('-'*3+ZeroOrMore('-'))

# Nums:
Digits         = string.digits
Integer        = Word(Digits)
Float          = Combine(Integer + '.' + Optional(Integer, default='0'))
            
# Literals and building blocks:
UCase          = string.uppercase
LCase          = string.lowercase
Letters        = UCase+LCase
VarChars       = Letters + Digits + '_'

# Atoms:
Numeric        = Float | Integer
VariableName   = Word(Letters, VarChars)
FunctionName   = VariableName
Variable       = VariableName('var')
FunctionCall   = Forward()

Integer.setParseAction(intParseAction)
Float.setParseAction(floatParseAction)
Variable.setParseAction(varParseAction)


#Operands, Operators:
Term           = Numeric | Variable



ArithExpression= operatorPrecedence( Term,
                                          [(signOp, 1, opAssoc.RIGHT, signParseAction),
                                           (multOp, 2, opAssoc.LEFT, multParseAction),
                                           (sumOp, 2, opAssoc.LEFT, sumParseAction)
                                           ])
#Boxes:


#Assignments:
VarAssignment  = Variable + "=" + ArithExpression
#TODO: Assignment.setParseAction(EvalAssignment)

def equationParseAction(s,loc,toks):
    return toks[0]-toks[1]

Equation       = ArithExpression("LHS") + Literal("=").suppress() + ArithExpression("RHS")
Equation.setParseAction(equationParseAction)

Operand = Numeric | Variable


#Subprograms:
SubProgram     = Forward()



FunctionCall   = FunctionName('name') + "(" + delimitedList(Operand, ",")('arguments')+ ")"
FunctionCall.setParseAction(generalParseAction)

def varListParseAction(s,loc,toks):
    l = []
    for tok in toks:
        l+=[tok]
    return [l]

VarList = delimitedList(Variable, ",")
VarList.setParseAction(varListParseAction)

Header         = FunctionName('name') + Literal("(").suppress() + VarList+ Literal(")").suppress()

NewLine    = (Optional(Literal("\r"))+Optional(Literal("\n"))).suppress()

SubBody           = Literal("{").suppress() +NewLine+ delimitedList(Equation("equation"), NewLine)+NewLine+ Literal("}").suppress()

def subBodyParseAction(s,loc,toks):
    l = []
    for tok in toks:
        l+=[tok]
    return [l]

SubBody.setParseAction(subBodyParseAction)

class CompiledSubProgram:
    def __init__(self,name, inputs, matrix, instances, outputs):
        self.name = name
        self.inputs= inputs
        self.matrix = matrix
        self.instances = instances
        self.outputs = outputs

    def isSolvable(self):
        s = solve(self.matrix, self.inputs+self.outputs)
        if s is None:
            return False
        for k in s:
            if s[k].is_Integer or s[k].is_Float:
                return False
        return True

    def __call__(self,in_list):
        if len(in_list)!=len(self.inputs):
            print 'INVALID NUMBER OF ARGS'
            return 0
        m1 = list(self.matrix)
        for i in range(len(self.inputs)):
            eq = self.inputs[i]-in_list[i]
            m1 += [eq]
        s = solve(m1,self.inputs+self.outputs)
        return s

    def __getitem__(self, key):
        return SingleCompiledSubProgram(self,self.outputs[key])

class SingleCompiledSubProgram:
    def __init__(self, sub_program, selected_output):
        self.sub_program = sub_program
        self.selected_output = selected_output

    def __call__(self,in_list):
        s = self.sub_program(in_list)
        if s is None:
            return s
        return int(s[self.selected_output])

            
        
        

def subProgramParseAction(s,loc,toks):
    global vars
    outputs = sub_lists(vars.values(), toks[1])
    csp = CompiledSubProgram(toks[0], toks[1], toks[2], vars, outputs)
    vars = {}
    return csp



    
    

SubProgram     << Header("header") + SubBody("body")
SubProgram.setParseAction(subProgramParseAction)

subprogram = """f(a,b){
 c = a+ b
a = 4 +d
}"""

if __name__=='__main__':
    header = 'Parser Tester begins:\n'
    print header + '=' * (len(header)-1)
    print subprogram
    print '-' * (len(header)-1) + '\n'
    results = SubProgram.parseString(subprogram)

    print '-' * (len(header)-1)

    #print type(results[0])

    print results[0].isSolvable()

    print results[0]([1,2])

    print results[0][1]([1,2])


